/*compile program g++ -lsocket homework1.cpp*/

#include <sys/socket.h>
#include <sys/types.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>

using namespace std;

int main(int argc, char **argv)
{

    //make sure both command line arguments are present
    if(argc != 3)
    {
        perror("Improper usage of command. Correct format: 'a.out n m' where n and m are integer values to generate random numbers");
        exit(1);
    }

    pid_t childOneProcessID, childTwoProcessID, child_pid;
    int stat_val;
    //next 3 variables are used to receive messages between processes
    int bufferFromP1toP2[1] = {0};
    int bufferFromP1toP3[1] = {0};
    int bufferFromP2toP3[1] = {0};
    //used by process P1 to communicate the random number generated between processes P2 and P3
    //also used by P2 to communicate random number to process P3
    int message[1];

    //socket creation for IPC between processes
    int socketFromP1toP2[2];
    int socketFromP1toP3[2];
    int socketFromP2toP3[2];

    int whichChild = 0;
    int amountOfRandNumsToGenerateFromParent = atoi(argv[1]);
    int amountOfRandNumsToGenerateFromChildOne = atoi(argv[2]);
    //sent by processes P2 and P3 to process P1 to inform P1 that the random number was received
    int ACKNOWLEDGE[1] = {1};
    //sent by P1 to P2 & P3 to indicate that P1 is finished generating random numbers
    int PARENT_FINISHED[1] = {-1};
    //sent by P2 to P3 to indicate P2 is done generating random numbers
    int CHILD_FINISHED[1] = {-1};
    
    //seed random number generator
    srand(time(NULL));

    //create socketpairs between processes
    if(socketpair(AF_UNIX, SOCK_STREAM, 0, socketFromP1toP2) < 0)
    {
        perror("socket creation from P1 to P2 failed\n");
        exit(1);
    }
    if(socketpair(AF_UNIX, SOCK_STREAM, 0, socketFromP1toP3) < 0)
    {
        perror("socket creation from P1 to P3 failed\n");
        exit(1);
    }
    if(socketpair(AF_UNIX, SOCK_STREAM, 0, socketFromP2toP3) < 0)
    {
        perror("socket creation from P2 to P3 failed\n");
        exit(1);
    }

    childOneProcessID = fork();

    if(childOneProcessID == 0)
    { //first child
        //close one end of each socket from P1 and P3
        close(socketFromP1toP2[0]);
        close(socketFromP2toP3[0]);
        //wait for a message from P1
        read(socketFromP1toP2[1], bufferFromP1toP2, sizeof(bufferFromP1toP2));

        //loop until P1 has finished generating random numbers
        while(bufferFromP1toP2[0] != PARENT_FINISHED[0])
        {
            if(bufferFromP1toP2[0] % 2)
                cout << "P2: The Number " << bufferFromP1toP2[0] << " is odd\n";
            else
                cout << "P2: The Number " << bufferFromP1toP2[0] << " is even\n";

            //let P1 know it received and printed the random number
            write(socketFromP1toP2[1], ACKNOWLEDGE, sizeof(ACKNOWLEDGE));
            //wait for another number from P1
            read(socketFromP1toP2[1], bufferFromP1toP2, sizeof(bufferFromP1toP2));
        }

        //once P1 has finished, generate random numbers to send to P3
        while(amountOfRandNumsToGenerateFromChildOne > 0)
        {
            //generate random number
            message[0] = 1 + rand() % 100;

            cout << "P2: The number " << message[0] << " being passed is for P3\n";
            //send random number to P3
            write(socketFromP2toP3[1], message, sizeof(message));
            //wait for an ACKNOWLEDGEment
            read(socketFromP2toP3[1], bufferFromP1toP3, sizeof(bufferFromP1toP3));
            
            amountOfRandNumsToGenerateFromChildOne--;
        }

        //let P3 know it is finished
        write(socketFromP2toP3[1], CHILD_FINISHED, sizeof(CHILD_FINISHED));
        //close remaining open ends of each socket pair, and then exit
        close(socketFromP2toP3[1]);
        close(socketFromP1toP3[1]);

        exit(0);
    }
    else if(childOneProcessID > 0)
    { // Parent process
        //create a second child process
        childTwoProcessID = fork();

        if(childTwoProcessID == 0)
        { //second child process
            //close one end of each socketpair from P2 and P1
            close(socketFromP2toP3[1]);
            close(socketFromP1toP3[1]);
            //wait for a response from P1
            read(socketFromP1toP3[0], bufferFromP1toP3, sizeof(bufferFromP1toP3));

            //continue reading from P1 until P1 has finished
            while(bufferFromP1toP3[0] != PARENT_FINISHED[0])
            {
                if(bufferFromP1toP3[0] % 2)
                    cout << "P3: The Number " << bufferFromP1toP3[0] << " is odd\n";
                else
                    cout << "P3: The Number " << bufferFromP1toP3[0] << " is even\n";
    
                write(socketFromP1toP3[0], ACKNOWLEDGE, sizeof(ACKNOWLEDGE));
                read(socketFromP1toP3[0], bufferFromP1toP3, sizeof(bufferFromP1toP3));

            }

            //wait for a response from P2 before proceeding
            read(socketFromP2toP3[0], bufferFromP2toP3, sizeof(bufferFromP2toP3));

            //keep reading from socket to P2 until P2 has finsihed
            while(bufferFromP2toP3[0] != CHILD_FINISHED[0])
            {
                if(bufferFromP2toP3[0] % 2)
                    cout << "P3: The Number " << bufferFromP2toP3[0] << " is odd\n";
                else
                    cout << "P3: The Number " << bufferFromP2toP3[0] << " is even\n";
    
                write(socketFromP2toP3[0], ACKNOWLEDGE, sizeof(int));
                read(socketFromP2toP3[0], bufferFromP2toP3, sizeof(bufferFromP2toP3));
        
            }

            //close other end of the socketpairs between P1 and P2, and exit
            close(socketFromP2toP3[0]);
            close(socketFromP1toP3[0]);
            exit(0);
                
        }

        //Parent process (P1) should generate n random numbers depending on command line argument
        while(amountOfRandNumsToGenerateFromParent > 0)
        {
            //determine which child to communicate with and generate the random number to send
            whichChild = 1 + rand() % 2;
            message[0] = 1 + rand() % 100;
        
            //close one end of the socket from P2 and P3
            close(socketFromP1toP2[1]);
            close(socketFromP1toP3[0]);

            cout << "P1: The number " << message[0] << " being passed is for ";

            //the following switch statement determines which child should be communicated with,
            //send the random number to that process (P2 or P3), wait for their ACKNOWLEDGEment,
            //and continue with the next random number
            switch(whichChild)
            {
                case 1: cout << "P2\n";
                        write(socketFromP1toP2[0], message, sizeof(message));
                        read(socketFromP1toP2[0], bufferFromP1toP2, sizeof(bufferFromP1toP2));
                        break;
                case 2: cout << "P3\n";
                        write(socketFromP1toP3[1], message, sizeof(message));
                        read(socketFromP1toP3[1], bufferFromP1toP3, sizeof(bufferFromP1toP3));
                        break;
            }

            amountOfRandNumsToGenerateFromParent--;
        }

        //inform processes P2 and P3 it has finished generating random numbers
        write(socketFromP1toP2[0], PARENT_FINISHED, sizeof(PARENT_FINISHED));
        write(socketFromP1toP3[1], PARENT_FINISHED, sizeof(PARENT_FINISHED));
        //close remaining ends of the socketpair from P2 and P3
        close(socketFromP2toP3[0]);
        close(socketFromP1toP3[1]);
        
        //wait for child processes to finish before exiting.
        child_pid = wait(&stat_val);
        cout << "Oh no! my children have died!\n";
        
        return 0;
    }
}
